[PATCH] http2: bound number of http2 frames per tx
authorPhilippe Antoine <pantoine@oisf.net>
Wed, 18 Feb 2026 15:40:23 +0000 (16:40 +0100)
committerAndreas Dolp <dev@andreas-dolp.de>
Thu, 23 Apr 2026 05:14:21 +0000 (07:14 +0200)
Ticket: 8289

If stream.reassembly.depth is unlimited,
an attacker controlling the 2 sides of a communication going through Suricata
can send a transition with an infinite number of headers, until suricata OOMs

Solution is to offer a configuration option to bound the number
of HTTP2 frames we store in a HTTP2 transaction, and produce an
anomaly if this bound is crossed

(cherry picked from commit 784e173278944c3596ea9cb219afcfafece6d156)

Origin: upstream, https://github.com/OISF/suricata/commit/82b7c9c35aaebf8a2811bdb703dd51c2fa0693c2.patch
Bug: https://redmine.openinfosecfoundation.org/issues/8296
Subject: Upstream fix for CVE-2026-31935

Gbp-Pq: Name CVE-2026-31935.patch

rules/http2-events.rules
rust/src/http2/http2.rs
suricata.yaml.in

index 8242e2f79e421980b231c006f1608fc32d6490cf..ff170e8ed6b77c8ee8af1d4ba59a21858ea71182 100644 (file)
@@ -22,3 +22,5 @@ alert http2 any any -> any any (msg:"SURICATA HTTP2 authority host mismatch"; fl
 alert http2 any any -> any any (msg:"SURICATA HTTP2 user info in uri"; flow:established,to_server; app-layer-event:http2.userinfo_in_uri; classtype:protocol-command-decode; sid:2290014; rev:1;)
 alert http2 any any -> any any (msg:"SURICATA HTTP2 reassembly limit reached"; flow:established; app-layer-event:http2.reassembly_limit_reached; classtype:protocol-command-decode; sid:2290015; rev:1;)
 alert http2 any any -> any any (msg:"SURICATA HTTP2 data on stream zero"; flow:established; app-layer-event:http2.data_stream_zero; classtype:protocol-command-decode; sid:2290018; rev:1;)
+# disabled by default, as it can happen in legit cases depending on the max-frames config value
+# alert http2 any any -> any any (msg:"SURICATA HTTP2 too many frames"; flow:established; app-layer-event:http2.too_many_frames; classtype:protocol-command-decode; sid:2290019; rev:1;)
index ac4079b4936e8b1fd9069664aa8b97d27fc80b49..9d3bf7488f24fc0d6f98f901f107f1f01f1134d0 100644 (file)
@@ -64,6 +64,7 @@ pub static mut HTTP2_MAX_TABLESIZE: u32 = 65536; // 0x10000
 // maximum size of reassembly for header + continuation
 static mut HTTP2_MAX_REASS: usize = 102400;
 static mut HTTP2_MAX_STREAMS: usize = 4096; // 0x1000
+static mut HTTP2_MAX_FRAMES: usize = 65536;
 
 #[repr(u8)]
 #[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)]
@@ -410,6 +411,7 @@ pub enum HTTP2Event {
     UserinfoInUri,
     ReassemblyLimitReached,
     DataStreamZero,
+    TooManyFrames,
 }
 
 pub struct HTTP2DynTable {
@@ -1068,16 +1070,18 @@ impl HTTP2State {
                     let ftype = head.ftype;
                     let sid = head.stream_id;
                     let padded = head.flags & parser::HTTP2_FLAG_HEADER_PADDED != 0;
-                    if dir == Direction::ToServer {
-                        tx.frames_ts.push(HTTP2Frame {
-                            header: head,
-                            data: txdata,
-                        });
+                    let h2frames = if dir == Direction::ToServer {
+                        &mut tx.frames_ts
                     } else {
-                        tx.frames_tc.push(HTTP2Frame {
+                        &mut tx.frames_tc
+                    };
+                    if h2frames.len() < unsafe { HTTP2_MAX_FRAMES } {
+                        h2frames.push(HTTP2Frame {
                             header: head,
                             data: txdata,
                         });
+                    } else {
+                        tx.tx_data.set_event(HTTP2Event::TooManyFrames as u8);
                     }
                     if ftype == parser::HTTP2FrameType::Data as u8 && sid == 0 {
                         tx.tx_data.set_event(HTTP2Event::DataStreamZero as u8);
@@ -1394,6 +1398,13 @@ pub unsafe extern "C" fn rs_http2_register_parser() {
                 SCLogError!("Invalid value for http2.max-streams");
             }
         }
+        if let Some(val) = conf_get("app-layer.protocols.http2.max-frames") {
+            if let Ok(v) = val.parse::<usize>() {
+                HTTP2_MAX_FRAMES = v;
+            } else {
+                SCLogError!("Invalid value for http2.max-frames");
+            }
+        }
         if let Some(val) = conf_get("app-layer.protocols.http2.max-table-size") {
             if let Ok(v) = val.parse::<u32>() {
                 HTTP2_MAX_TABLESIZE = v;
index 9cdc8243028960301fda7a05d943727746e40971..374f4dd0bac0c187239064f66d0eeb5bd79fa1f4 100644 (file)
@@ -951,6 +951,8 @@ app-layer:
       #max-table-size: 65536
       # Maximum reassembly size for header + continuation frames
       #max-reassembly-size: 102400
+      # Maximum number of frames per tx
+      #max-frames: 65536
     smtp:
       enabled: yes
       raw-extraction: no